
    /* one client, one server, asynchronous send-receive */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <sys/signal.h>

#define BUFFER_SIZE 1024
#define MAX_QUE_CONN_NM 5

#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while(0)

    /* get a string of length n-1 from stdin and clear the stdin buffer */
char* s_gets(char* stdin_buf, int n)
 {
    char* ret_val;
    int i = 0;

    ret_val = fgets(stdin_buf, n, stdin);
    if(ret_val) {
        while(stdin_buf[i] != '\n' && stdin_buf[i] != '\0') {
            i++;
        }
        if(stdin_buf[i] == '\n') {
            stdin_buf[i] = '\0';
        } else {
            while (getchar() != '\n') ;
        }
    }

    return ret_val;
}

    /* save the ipv4 address of this server to ip_addr */
int getipv4addr(char *ip_addr)
{
    struct ifaddrs *ifaddrsptr = NULL;
    struct ifaddrs *ifa = NULL;
    void *tmpptr = NULL;
    int ret;
    
    ret = getifaddrs(&ifaddrsptr);
    if (ret == -1) {
        ERR_EXIT("getifaddrs()");
    }

    for(ifa = ifaddrsptr; ifa != NULL; ifa = ifa->ifa_next) {
        if(!ifa->ifa_addr) {
            continue;
        }
        if(ifa->ifa_addr->sa_family == AF_INET) { /* AF_INET: ipv4 */
            tmpptr = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
            char addr_buf[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, tmpptr, addr_buf, INET_ADDRSTRLEN);
            printf("%s ipv4 address = %s\n", ifa->ifa_name, addr_buf);
            if (strcmp(ifa->ifa_name, "lo") != 0) {
                strcpy(ip_addr, addr_buf); /* save the ipv4 address */
            }
        } else if(ifa->ifa_addr->sa_family == AF_INET6) { /* ipv6 */
            tmpptr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
            char addr_buf[INET6_ADDRSTRLEN];
            inet_ntop(AF_INET6, tmpptr, addr_buf, INET6_ADDRSTRLEN);
            printf("%s ipv6 address %s\n", ifa->ifa_name, addr_buf);
        }
    }

    if (ifaddrsptr != NULL) {
        freeifaddrs(ifaddrsptr);
    }

    return EXIT_SUCCESS;
}


int main(void)
{
    int server_fd, connect_fd;
    struct sockaddr_in server_addr, connect_addr;
    socklen_t addr_len;
    int recvbytes, sendbytes, ret;
    char send_buf[BUFFER_SIZE], recv_buf[BUFFER_SIZE];
    char ip_addr[INET_ADDRSTRLEN]; /* ipv4 address */
    char stdin_buf[BUFFER_SIZE];
    uint16_t port_num;
    pid_t childpid;

    server_fd = socket(AF_INET, SOCK_STREAM, 0); /* create an ipv4 server*/
    if(server_fd == -1) {
        ERR_EXIT("socket()");
    }
    printf("server_fd = %d\n", server_fd);

    ret = getipv4addr(ip_addr); /* get server ipv4 address */
    if (ret == EXIT_FAILURE) {
        ERR_EXIT("getifaddrs()");
    }
    
        /* set sockaddr_in */
    server_addr.sin_family = AF_INET; /* ipv4 */
    server_addr.sin_port = 0; /* auto server port number */
    server_addr.sin_addr.s_addr = inet_addr(ip_addr);
    bzero(&(server_addr.sin_zero), 8); /* padding with 0s */

    int opt_val = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt_val, sizeof(opt_val)); /* many options */
    
    addr_len = sizeof(struct sockaddr);
    ret = bind(server_fd, (struct sockaddr *)&server_addr, addr_len);
    if(ret == -1) {
        close(server_fd);
        ERR_EXIT("bind()");    
    }
    printf("Bind success!\n");

    addr_len = sizeof(server_addr);
    ret = getsockname(server_fd, (struct sockaddr *)&server_addr, &addr_len);
    if(ret == 0) {
        printf("Server port number = %d\n", ntohs(server_addr.sin_port));
    } else {
        close(server_fd);
        ERR_EXIT("Server port number unknownn");
    }
    ret = listen(server_fd, MAX_QUE_CONN_NM); 
    if(ret == -1) {
        close(server_fd);
        ERR_EXIT("listen()");
    }
    printf("Server ipv4 addr: %s\n", ip_addr);
    printf("Listening ...\n");
    
    addr_len = sizeof(struct sockaddr);
        /* addr_len should be refreshed before each accept() */
    connect_fd = accept(server_fd, (struct sockaddr *)&connect_addr, &addr_len);
        /* waiting for connection */
    if(connect_fd == -1) {
        close(server_fd);
        ERR_EXIT("accept()");
    }

    port_num = ntohs(connect_addr.sin_port);
    strcpy(ip_addr, inet_ntoa(connect_addr.sin_addr));
    printf("Client accepted: IP addr = %s, port = %hu\n", ip_addr, port_num);

    childpid = fork();
    if(childpid < 0) {
        close(server_fd);
        close(connect_fd);
        ERR_EXIT("fork()");
    }
    if(childpid > 0) { /* parent pro */
        while(1) { /* sending cycle */
            memset(send_buf, 0, BUFFER_SIZE);
            s_gets(send_buf, BUFFER_SIZE); /* without endding '\n' */
            sendbytes = send(connect_fd, send_buf, strlen(send_buf), 0); /* blocking send */
            if(sendbytes <= 0) {
                printf("sendbytes = %d. Connection terminated ...\n", sendbytes);
                break;
            }
            if(strncmp(send_buf, "end", 3) == 0) {
                break;
            }
        }
        close(server_fd);
        close(connect_fd);
        kill(childpid, SIGKILL);
    }
    else { /* child pro */
        while(1) { /* receiving cycle */
            memset(recv_buf, 0, BUFFER_SIZE);
            recvbytes = recv(connect_fd, recv_buf, BUFFER_SIZE, 0); /* blocking receive */
            if(recvbytes <= 0) {
                printf("recvbytes = %d. Connection terminated ...\n", recvbytes);
                break;
            }
            printf("\t\t\t\t\tClient %s >>>> %s\n", ip_addr, recv_buf);
            if(strncmp(recv_buf, "end", 3) == 0) {
                break;
            }
        }
        close(connect_fd);
        close(server_fd);
        kill(getppid(), SIGKILL);
    }

    return EXIT_SUCCESS;
}

